[007] [STM32] 以面向对象的思想编写按键程序

您所在的位置:网站首页 单片机 gpio 开源框架 [007] [STM32] 以面向对象的思想编写按键程序

[007] [STM32] 以面向对象的思想编写按键程序

2024-07-09 14:17:39| 来源: 网络整理| 查看: 265

STM32 Contents CubeMX配置 按键管理框架 按键驱动层 按键驱动框架层 按键管理层 应用示例

本文为百问网&韦东山【物联网智能家居实战训练营】课程笔记

声明:本项目参考:MultiButton

1 CubeMX配置

在这里插入图片描述

RCC:配置HSE为晶体/陶瓷谐振器SYS:Debug选择SW模式时钟:选择HSE作为PLL时钟源,将其改为8MHz(默认),然后选择PLLCLK作为系统时钟源,设为系统时钟为72MHz按键引脚:PA0(wk_up)下拉输入,PA15(key1)和PC5(key0)上拉输入LED引脚:PA8(红灯)和PD2(黄灯)设为推挽输出,默认输出高电平TIM6:向上计数模式(TIM6&7只支持向上计数),psc = 72 - 1,arr = 1000 - 1,即1ms更新中断,然后配置NVIC使能TIM6全局中断USART1:TX-PA9,RX-PA10,用于打印按键触发事件信息以单独的c/h文件生成MDK工程 2 按键管理框架 2.1 按键驱动层

按键底层驱动通过CubeMX配置相应引脚GPIO模式,然后调用HAL库初始化:

void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); GPIO_InitStruct.Pin = WKUP_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(WKUP_GPIO_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = KEY0_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(KEY0_GPIO_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = KEY1_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct); } 2.2 按键驱动框架层

按键驱动框架比较简单,将获取IO电平的HAL库函数二次封装一下即可:

uint8_t wkup_key_read_pin(void) { return HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin); } uint8_t key0_read_pin(void) { return HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin); } uint8_t key1_read_pin(void) { return HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin); } 2.3 按键管理层 2.3.1 按键对象状态 enum key_state { Init_None_State = 0, /* 初始未按下状态 */ Init_Press_State, /* 初次按下状态 */ Press_Check_State, /* 连击检查状态 */ Continuous_Press_State, /* 连续按下状态 */ Long_Press_State, /* 长按状态 */ }; 2.3.2 按键对象事件 enum key_event { Press_Down = 0, /* 按键按下,每次按下都触发 */ Press_Up, /* 按键弹起,每次松开都触发 */ Singe_Click, /* 单击触发(仅触发一次) */ Double_Click, /* 双击触发(仅触发一次) */ Short_Press_Repeat, /* 每次短按时都会触发(按下次数>=2) */ Long_Press_Start, /* 首次进入长按状态触发(仅触发一次) */ Long_Press_Hold, /* 长按保持状态触发(每经过一个循环长按间隔触发一次) */ Event_Sum, /* 事件总数 */ None_Press /* 未按下 */ }; 2.3.3 按键对象句柄结构体 struct key_handle { uint16_t tick; /* 按键系统时间片 */ uint8_t repeat_cnt : 4; /* 按键短按次数 */ uint8_t event : 4; /* 触发事件 */ uint8_t state : 3; /* 按键状态 */ uint8_t debounce_tick : 3; /* 消抖时间片 */ uint8_t active_level : 1; /* 按键有效按下电平 */ uint8_t key_level : 1; /* 按键引脚当前电平 */ uint8_t (* pin_read)(void); /* 获取按键引脚电平 */ void (* event_callback[Event_Sum])(struct key_handle* key); /* 按键事件回调函数 */ struct key_handle* next; /* 单向链表next指针 */ }; typedef struct key_handle *key_handle_t; 2.3.4 初始化按键对象 static key_handle_t _key_slist_head = NULL; // 按键管理单链表头结点 /** * @brief 初始化按键对象 * @param key 按键对象句柄 * @param gpio_pin_read 获取按键电平函数指针 * @param active_level 按键按下有效电平 * @return 0: succeed -1: failed */ int8_t key_init(struct key_handle *key, uint8_t (*gpio_pin_read)(void), uint8_t active_level) { if (key == NULL) return -1; memset(key, 0, sizeof(struct key_handle)); key->event = None_Press; key->active_level = active_level; key->pin_read = gpio_pin_read; key->key_level = key->pin_read(); return 0; } 2.3.5 注册按键 /** * @brief 注册按键:将按键对象插入到按键管理链表中 * @param key 按键对象句柄 * @return 0: succeed -1: failed */ int8_t key_handle_register(struct key_handle *key) { struct key_handle *key_slist_node = _key_slist_head; // 获取头指针的地址 (无头结点单链表) if (key == NULL) return -1; // 尾插(不带头结点的单链表, 头指针需做特殊判断) if (_key_slist_head == NULL) // 头指针为空==表空 { _key_slist_head = key; key->next = NULL; return 0; } while(key_slist_node) { if (key_slist_node == key) return -1; // 重复注册 if(key_slist_node->next == NULL) break; // 已经遍历到最后一个节点,必须在此跳出循环, 否则key_slist_node==NULL key_slist_node = key_slist_node->next; } key_slist_node->next = key; key->next = NULL; return 0; } 2.3.6 脱离按键 /** * @brief 脱离按键:将按键对象从按键管理链表中脱离 * @param key 按键对象句柄 * @return 0: succeed -1: failed */ int8_t key_handle_detach(struct key_handle *key) { // 解1级引用指向指针变量, 解2级引用指向指针变量所指向的变量 struct key_handle **key_slist_node = &_key_slist_head; // 指向头指针, 直接操作原指针变量(不然最后无法修改头指针) struct key_handle *node_temp; if (key == NULL || _key_slist_head == NULL) return -1; while(*key_slist_node && *key_slist_node != key) { node_temp = *key_slist_node; if((*key_slist_node)->next == NULL) break; key_slist_node = &node_temp->next; // 不能直接解1级引用赋值,会破坏原链表 } if (*key_slist_node != key) return -1; *key_slist_node = (*key_slist_node)->next; return 0; } 2.3.7 注册事件回调函数 /** * @brief 注册按键事件触发回调函数 * @param key 按键对象句柄 * @param event 触发事件类型 * @param event_callback 事件回调函数 * @return 0: succeed -1: failed */ int8_t key_event_callback_register(struct key_handle *key, uint8_t event, void (* event_callback)(key_handle_t key)) { if (key == NULL || event >= Event_Sum) return -1; key->event_callback[event] = event_callback; return 0; } 2.3.8 按键状态机

在这里插入图片描述

状态符号按键对象状态含义state0Init_None_State初始未按下状态state1Init_Press_State初次按下状态state2Press_Check_State连击检查状态state3Continuous_Press_State连续按下状态state5Long_Press_State长按状态 /** * @brief 处理所有按键对象的状态机 * @param key 按键对象句柄 * @return None */ static void key_handler(struct key_handle *key) { uint8_t key_level_temp = key->pin_read(); if(key->state != Init_None_State) key->tick++; /* 按键消抖(按键状态发生变化保持DEBOUNCE_TICK时间片开始保存按键引脚电平) */ if(key_level_temp != key->key_level) { if(++(key->debounce_tick) >= DEBOUNCE_TICK) { key->key_level = key_level_temp; key->debounce_tick = 0; } } else { key->debounce_tick = 0; } /* 按键状态机 */ switch (key->state) { case Init_None_State: /* 初始态-> 初始按下态 Press_Down */ if(key->key_level == key->active_level) { key->event = (uint8_t)Press_Down; __KEY_EVENT_CALL(Press_Down); key->tick = 0; key->repeat_cnt = 1; key->state = Init_Press_State; } else { key->event = (uint8_t)None_Press; } break; case Init_Press_State: /* 第一次按下松开:初始按下态->连击检查态 Press_Up */ if(key->key_level != key->active_level) { key->event = (uint8_t)Press_Up; __KEY_EVENT_CALL(Press_Up); key->tick = 0; key->state = Press_Check_State; } /* 第一次按下后长按(>LONG_PRESS_START_TICK):初始按下态->长按态 Long_Press_Start */ else if(key->tick > LONG_PRESS_START_TICK) { key->event = (uint8_t)Long_Press_Start; __KEY_EVENT_CALL(Long_Press_Start); key->state = Long_Press_State; } break; case Press_Check_State: /* 松开后再次按下:连击检查态->连击态 Press_Down & Short_Press_Repeat */ if(key->key_level == key->active_level) { key->event = (uint8_t)Press_Down; __KEY_EVENT_CALL(Press_Down); key->repeat_cnt++; __KEY_EVENT_CALL(Short_Press_Repeat); key->tick = 0; key->state = Continuous_Press_State; } /* 松开后再次没有按下(>SHORT_PRESS_START_TICK):连击检查态->初始态 repeat_cnt=1: Singe_Click; repeat_cnt=2: Double_Click */ else if(key->tick > SHORT_PRESS_START_TICK) { if(key->repeat_cnt == 1) { key->event = (uint8_t)Singe_Click; __KEY_EVENT_CALL(Singe_Click); } /* 连击态松开后会返回此条件下触发 todo: */ else if(key->repeat_cnt == 2) { key->event = (uint8_t)Double_Click; __KEY_EVENT_CALL(Double_Click); } key->state = Init_None_State; } break; case Continuous_Press_State: /* 连击后松开:连击态->连击检查态(< SHORT_PRESS_START_TICK)) : 连击态->初始态(>= SHORT_PRESS_START_TICK) */ if(key->key_level != key->active_level) { key->event = (uint8_t)Press_Up; __KEY_EVENT_CALL(Press_Up); if(key->tick key->state = Init_None_State; } } /* 连击后长按(>SHORT_TICKS): 连击态 -> 初始态 */ else if(key->tick > SHORT_PRESS_START_TICK) { key->state = Init_Press_State; // 可以回到Init_None_State/Init_Press_State } break; case Long_Press_State: /* 长按保持 Long_Press_Hold */ if(key->key_level == key->active_level) { key->event = (uint8_t)Long_Press_Hold; if (key->tick % LONG_HOLD_CYCLE_TICK == 0) { __KEY_EVENT_CALL(Long_Press_Hold); } } /* 长按松开:长按态-> 初始态 */ else { key->event = (uint8_t)Press_Up; __KEY_EVENT_CALL(Press_Up); key->state = Init_None_State; } break; } } 2.3.9 遍历调用按键对象 /** * @brief 每经过一个滴答周期调用一次按键处理函数(裸机放1ms中断, OS放线程或中断) * @param None * @return None */ void key_tick(void) { struct key_handle *key_slist_node; static uint8_t tick_cnt = 0; if (++tick_cnt next) { key_handler(key_slist_node); } tick_cnt = 0; }

注意:裸机环境下,函数必须在中断中(默认1ms,可修改)调用,上RTOS可以在线程上下文中检测。

3 应用示例 static struct key_handle _key0; static void key0_event_callback(key_handle_t key) { switch(key->event) { case Press_Down: UART_DEBUG("key0 press down"); break; case Press_Up: UART_DEBUG("key0 press up"); break; case Singe_Click: UART_DEBUG("key0 single click"); break; case Double_Click: UART_DEBUG("key0 double click"); break; case Short_Press_Repeat: UART_DEBUG("key0 short press repeat"); break; case Long_Press_Start: UART_DEBUG("key0 long press start"); break; case Long_Press_Hold: key_handle_detach(&_key0); UART_DEBUG("key0 long press hold"); break; default: break; } } void key_test_sample(void) { key_init(&_key0, key0_read_pin, GPIO_PIN_RESET); key_event_callback_register(&_key0, Press_Down, key0_event_callback); key_event_callback_register(&_key0, Press_Up, key0_event_callback); key_event_callback_register(&_key0, Singe_Click, key0_event_callback); key_event_callback_register(&_key0, Double_Click, key0_event_callback); key_event_callback_register(&_key0, Short_Press_Repeat, key0_event_callback); key_event_callback_register(&_key0, Long_Press_Start, key0_event_callback); key_event_callback_register(&_key0, Long_Press_Hold, key0_event_callback); key_handle_register(&_key0); }

在定时器中断回调函数里检测:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM6) { key_tick(); } }

串口打印信息: 在这里插入图片描述

代码已开源到gitee:以面向对象思想编写的按键管理框架

END



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭